/*
 * File:   LevelParser.cpp
 * Author: RedEyedKiller
 *
 * Created on 2 Μάρτιος 2010, 12:12 πμ
 */

//#define TIXML_USE_STL

#include "tinyxml.h"
#include "LevelParser.h"
#include "../LevelMap.h"
#include "../Tile.h"
#include "../Utilities.h"
#include "EnviromentParser.h"
#include "../Globals.h"
#include <iostream>
#include "../ResourceManager.h"
#include "../Logger.h"
#include "../ServiceLocator.h"
#include "../StateStack.h"
#include "../State.h"

namespace FileParser
{

LevelParser::LevelParser(const char* name, std::list<std::string>* playlist) : fname(name)
{
    this->playlist = playlist;
}

LevelParser::~LevelParser()
{

}

bool LevelParser::ParseLevel(LevelMap* cacheMap, TileProperties* prop)
{
    //Load file
    TiXML::TiXmlDocument xFile( ( Globals::GetInstance( )->GetPath( "Maps" ) + fname ).c_str( ) );
    if( !xFile.LoadFile( ) ) return false;

    //get the root of the file : map
    TiXML::TiXmlElement* xMap = xFile.FirstChildElement( "map" );
    if( !xMap )
        return false;
    else
    {
        int width, height;
        int tileH, tileW;
        xMap->Attribute( "width", &width );
        xMap->Attribute( "height", &height );
        xMap->Attribute( "tileheight", &tileH );
        xMap->Attribute( "tilewidth", &tileW );
        //allocate the map with the given attributes
        cacheMap->SetSize( width, height );
        cacheMap->SetTileSize( tileW, tileH );
    }

    //parse the basic properties if the region
    //as playlist and level name
    if( xMap->FirstChildElement( "properties" ) )
        ParseMapProperties( xMap->FirstChildElement( "properties" )->FirstChildElement( "property" ), cacheMap );

    //parse the tilesets used by map
    ParseTileset( xMap->FirstChildElement( "tileset" ), cacheMap, prop );

    //parse the layers used by map
    ParseLayers( xMap->FirstChildElement( "layer" ), cacheMap );

    //Register tile properties
    RegisterTileProperties( cacheMap );

    //parse the object layers
    ParseObjectLayer( xMap->FirstChildElement( "objectgroup" ), cacheMap );

    xMap->Clear( );
    xFile.Clear( );
    if( xFile.Error( ) )
    {
        std::cerr << "Clear " << xFile.ErrorDesc( ) << std::endl;
    }

    return true;
}

bool LevelParser::ParseTileset(TiXML::TiXmlElement* xTileset, LevelMap* cacheMap, TileProperties* prop)
{
    //while there are more tilesets
    while( xTileset )
    {
        //extract data from file
        int fGid;
        xTileset->Attribute( "firstgid", &fGid );

        TiXML::TiXmlElement* image = xTileset->FirstChildElement( "image" );
        std::string path = image->Attribute( "source" );
        image->Clear( );

        ParseTiles( xTileset->FirstChildElement( "tile" ), cacheMap, prop );

        //push the extracted data into the cached map
        cacheMap->RegisterTileset( Globals::GetInstance( )->GetPath( "Maps" ), path, fGid );

        {//store the element in a temp variable in order to clear the last tileset
            TiXML::TiXmlElement* temp = xTileset->NextSiblingElement( "tileset" );
            xTileset->Clear( );
            xTileset = temp;
        }
    }
    return true;
}

bool LevelParser::ParseTiles(TiXML::TiXmlElement* xTile, LevelMap* cacheMap, TileProperties* prop)
{
    while( xTile )
    {
        int gid;
        xTile->Attribute( "id", &gid );

        TiXML::TiXmlElement* xProps = xTile->FirstChildElement( "properties" );
        TiXML::TiXmlElement* xProp = xProps->FirstChildElement( "property" );
        long flag;
        flag = 0;
        while( xProp )
        {
            flag |= prop->ValueOf( xProp->Attribute( "name" ) );

            {//store the element in a temp variable in order to clear the last property
                TiXML::TiXmlElement* temp = xProp->NextSiblingElement( "property" );
                xProp->Clear( );
                xProp = temp;
            }
        }

        tiles.push_back( std::pair<unsigned int, unsigned long>( gid, flag ) );
        {//store the element in a temp variable in order to clear the last tile
            TiXML::TiXmlElement* temp = xTile->NextSiblingElement( "tile" );
            xTile->Clear( );
            xTile = temp;
        }
    }
    return true;
}

bool LevelParser::ParseLayers(TiXML::TiXmlElement* xLayer, LevelMap* cacheMap)
{
    while( xLayer )
    {
        {
            TiXML::TiXmlElement* data = xLayer->FirstChildElement( "data" );

            //extract dimensions of map
            int width, height;
            xLayer->Attribute( "width", &width );
            xLayer->Attribute( "height", &height );

            //retrieve all tile numbers from the file
            std::string panel( ( char* ) data->GetText( ) );

            //loop through each tile
            ParseCSV( panel, cacheMap );
            data->Clear( );
            //IN FUTURE: retrieve layer properties , if any
        }
        //get next and clear the previous
        TiXML::TiXmlElement* tmp = xLayer->NextSiblingElement( "layer" );
        xLayer->Clear( );
        xLayer = tmp;
    }
    return true;
}

bool LevelParser::ParseObjectLayer(TiXML::TiXmlElement* xOLayer, LevelMap* cacheMap)
{
    while( xOLayer )
    {
        {
            //figure out what this object layer represents
            std::string name( xOLayer->Attribute( "name" ) );

            std::string renderingLayer( xOLayer->Attribute( "renderingLayer" ) );

            StateStack *stateStack = ServiceLocator< StateStack >::GetService( );

            RenderingManager *renderingManager = stateStack->Top( )->GetRenderingManager( );

            if( !renderingManager->LayerExists( renderingLayer ) )
            {
                double pFactor;
                xOLayer->Attribute( "pFactor", &pFactor );
                renderingManager->AddLayer( renderingLayer, pFactor );
            }

            if( name == "SpawnLayer" )
            {
                SpawnPointHandler sph;
                sph.layer = renderingLayer;
                ParsePoint( xOLayer, sph );
            }
            else if( name == "FreeTiles" )
            {
                FreeTilePointHandler ftph;
                ftph.cacheMap = cacheMap;
                ftph.layer = renderingLayer;
                ParsePoint( xOLayer, ftph );
            }
        }
        //get next object layer
        TiXML::TiXmlElement* tmp = xOLayer->NextSiblingElement( "objectgroup" );
        xOLayer->Clear( );
        xOLayer = tmp;
    }
    return true;
}

bool LevelParser::ParseMapProperties(TiXML::TiXmlElement* xProp, LevelMap* cacheMap)
{
    while( xProp )
    {
        std::string name( xProp->Attribute( "name" ) );
        if( name == "BGM" )
        {
            if( playlist != NULL )
            {
                std::string value( xProp->Attribute( "value" ) );
                playlist->push_back( value );
            }
        }
        else if( name == "Name" )
        {
            std::string value( xProp->Attribute( "value" ) );
            //do something with the value
        }
        else if( name == "Enviroment" )
        {
            //get the filename of enviromental data
            std::string envFname( xProp->Attribute( "value" ) );
            //parse enviromental with parser
            EnviromentParser eParser;
            eParser.ParseEnviroment( envFname );
        }
        else if( name == "Background" )
        {
            //get the background image of the map
            std::string background( xProp->Attribute( "value" ) );
            RenderingManager* rendMan = ServiceLocator<StateStack>::GetService( )->Top( )->GetRenderingManager( );
            rendMan->AddLayer( "__backImage", 0.1 );
            rendMan->SendToBack( "__backImage" );
            cacheMap->SetBackground( ResourceManager::GetInstance( )->GetTexture( background.c_str( ) ) );
            rendMan->RegisterDrawable( cacheMap->GetBackground( ), "__backImage" );
        }
        else
        {
            LOG( Logger::CHANNEL_LOADING, LogFileStream::LEVEL_ERROR ) << "Error parsing xml map value = " + name;
        }

        {//store the element in a temp variable in order to clear the last element
            TiXML::TiXmlElement* temp = static_cast < TiXML::TiXmlElement* > ( xProp->NextSibling( "property" ) );
            xProp->Clear( );
            xProp = temp;
        }
    }
    return true;
}

bool LevelParser::ParsePoint(TiXML::TiXmlElement* xOLayer, const PointHandler& handler)
{
    TiXML::TiXmlElement* xPoint = xOLayer->FirstChildElement( "object" );
    while( xPoint )
    {
        {
            //extract data
            const char* nameRaw = xPoint->Attribute( "name" );
            const char* typeRaw = xPoint->Attribute( "type" );

            std::string name;
            std::string type;

            if( nameRaw )
                name = nameRaw;

            if( typeRaw )
                type = typeRaw;

            int x, y;
            xPoint->Attribute( "x", &x );
            xPoint->Attribute( "y", &y );
            //save data in a structure
            handler( Math::Vector2I( x, y ), type, name, xPoint );
        }
        TiXML::TiXmlElement* tmp = xPoint->NextSiblingElement( "object" );
        xPoint->Clear( );
        xPoint = tmp;
    }
    return true;
}

bool LevelParser::ParseCSV(std::string& data, LevelMap* cacheMap)
{
    std::istringstream iss( data );
    char trash;
    for( int j = 0; j < cacheMap->GetHeight( ); j++ )
    {
        for( int i = 0; i < cacheMap->GetWidth( ); i++ )
        {
            unsigned int tmp;
            iss >> tmp;
            //if null tile do not register it
            if( tmp != 0 )
            {
                Tile tile( tmp );
                cacheMap->RegisterTile( tile, j, i );
            }
            iss >> trash;
            if( trash != ',' )
            {
                // std::cerr << "Probably an error in csv parsing occured" << std::endl;
                return false;
            }
        }
    }
    return true;
}

void LevelParser::RegisterTileProperties(LevelMap* cacheMap)
{
    int size = tiles.size( );
    for( int i = 0; i < size; ++i )
    {
        //register the flag to the gid + 1 due to the first 0 gid which we add
        //afterwards.
        cacheMap->RegisterTileProperties( tiles[i].first + 1, tiles[i].second );
    }
}

void SpawnPointHandler::ParseProperties(TiXML::TiXmlElement* xProps, EntitySystem::Entity* entity) const
{
    if( !xProps )return;
    //TiXML::TiXmlElement* xProperty = xProps->FirstChildElement( );
    //FIXME enable property parsing
    /*
    while( xProperty )
    {
        //get properties name
	
        const char* name = xProperty->Attribute( "name" );
        
        CScriptAny* any = entity->GetProperty( name );
        if( any )
        {
            //time for the value
            double value;
            xProperty->Attribute( "value", &value );
        }
        //get the next xml property, if any
        TiXML::TiXmlElement* tmp = xProperty->NextSiblingElement( );
        xProperty->Clear( );
        xProperty = tmp;
    }
     */
}

void FreeTilePointHandler::operator( )( const Math::Vector2I& point, const std::string& type, const std::string& name, TiXML::TiXmlElement* xPoint ) const
{
    //get the tile from map with its properties
    int gid;
    xPoint->Attribute( "gid", &gid );
    Tile tile;
    if( !cacheMap->GetTile( gid, tile ) )
    {
        LOG( Logger::CHANNEL_LOADING, LogFileStream::LEVEL_ERROR ) << "Tile " << gid << " is not registered.";
        return;
    }

    StateStack *stateStack = ServiceLocator< StateStack >::GetService( );
    State *currState = stateStack->Top( );
    EntityManager *entManager = currState->GetEntityManager( );
    Entity* spawned = NULL;

    //create it if it exists as a prototype
    if( !type.empty( ) && entManager->CheckPrototype( type ) )
    {
        spawned = entManager->RequestEntityImidAt( type, point.GetX( ), point.GetY( ) - cacheMap->GetTileHeight( ) );
    }
    else
    {
        //if no type was specified asume its type is __tileXX 
        std::ostringstream oss;
        oss << "__tile" << gid;
        if( entManager->CheckPrototype( oss.str( ) ) )
            spawned = entManager->RequestEntityImidAt( oss.str( ), point.GetX( ), point.GetY( ) - cacheMap->GetTileHeight( ) );
    }

    //if failed it means we must create a new entity to represent this tile
    //and register it as prototype in order
    //in the future to be created by the EntityCreator and not us.
    if( spawned == NULL )
    {
        spawned = new Entity;
        //create entity's drawlogic
        AnimationLogic *animeLogic = new AnimationLogic;
        animeLogic->SetDisplay( tile.GetTileset( )->GetResource( ) );

        AnimationFrame frame;
        frame.firstFrame = tile.GetClip( );

        frame.numberOfFrames = 1;
        frame.totalTime = 10;
        frame.flip[0] = frame.flip[1] = false;

        animeLogic->AddFrame( "DEF", frame );
        animeLogic->SetDefaultAnimation( "DEF" );
        animeLogic->SetCurrentAnimation( "DEF" );
        animeLogic->SetPlaying( false );

        spawned->SetAnimationLogic( animeLogic );

        //if entity is solid create its physics logic
        if( tile.GetFlag() != 0 )
        {
            PhysicsLogic* pLogic = new PhysicsLogic;
            pLogic->SetMass( 0 );
            pLogic->SetRect( Math::Rect( 0, 0, cacheMap->GetTileWidth( ), cacheMap->GetTileHeight( ) ) );
            pLogic->SetGhost(tile.CheckFlag( 1 ));
            spawned->SetPhysicsLogic( pLogic );
        }

        //register the entity as a prototype and request a new one
        //entity MUST NOT be deleted or otherwise the registered prototype will be deleted too.
        if( !type.empty( ) )
        {
            spawned->SetType( type );
            entManager->RegisterEntityPrototype( spawned, type );
            spawned = entManager->RequestEntityImidAt( type, point.GetX( ), point.GetY( ) - cacheMap->GetTileHeight( ) );
        }
        else
        {
            std::ostringstream oss;
            oss << "__tile" << gid;
            spawned->SetType( oss.str( ) );
            entManager->RegisterEntityPrototype( spawned, oss.str( ) );
            spawned = entManager->RequestEntityImidAt( oss.str( ), point.GetX( ), point.GetY( ) - cacheMap->GetTileHeight( ) );
        }
    }
    //Whichever way gave birth to our beloved entity logic registration is always the same.   
    DrawLogic* drawLogic = spawned->GetAnimationLogic( );
    if( drawLogic != NULL )
    {
        RenderingManager *renderer = currState->GetRenderingManager( );
        renderer->RegisterDrawable( drawLogic, layer );
    }

    PhysicsLogic *physicsLogic = spawned->GetPhysicsLogic( );
    if( physicsLogic != NULL )
    {
        PhysicsManager *physicsManager = currState->GetPhysicsManager( );
        physicsManager->AddPhysicsLogic( physicsLogic );
    }
}

};
